/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the 
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/module.h>

#include "mmrp_attribute_values.h"
#include "mmrp_private.h"
#include "mmrp_mrpdu.h"
#include "mmrp.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Philip Foulkes <philip.foulkes@gmail.com>");
MODULE_DESCRIPTION("Multiple MAC Reservation Protocol");

struct mmrp
{
	struct mrp mrp; /* has to be first */
};


static struct mmrp mmrp;


// module parameters ===============================
static char * port_name = "eth0";
module_param (port_name, charp, S_IRUGO);
// =================================================


// ================================================================
// ops ============================================================
static int mmrp_mad_join_indication (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type, int new)
{
    int i;
    
    MMRP_PRINT_INFO ("%s %s\n", __FUNCTION__, "called");
    
    if (mrp_attribute_type == MMRP_ATTRIBUTE_TYPE_MAC)
    {
        MMRP_PRINT_INFO("%s\n", "Received MAC address");
        for(i = 0; i < MSRP_ATTRIBUTE_LENGTH_MAC; ++i)
            printk (KERN_INFO "%x\n", ((u8*)attribute_value)[i]);
    }    
    else if (mrp_attribute_type == MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT)
    {
        MMRP_PRINT_INFO("%s %x\n", "Received Service Requirement: ", (u8)(*((u8*)attribute_value)));
    }
    else
    {
        MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "called with unknown mrp_attribute_type");
        return -EINVAL;
    }
    
    return 0;
}

static int mmrp_mad_leave_indication (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type)
{
    int i;
    
    MMRP_PRINT_INFO ("%s %s\n", __FUNCTION__, "called");
    
    if (mrp_attribute_type == MMRP_ATTRIBUTE_TYPE_MAC)
    {
        MMRP_PRINT_INFO("%s\n", "Received MAC address");
        for(i = 0; i < MSRP_ATTRIBUTE_LENGTH_MAC; ++i)
            printk (KERN_INFO "%x ", ((u8*)attribute_value)[i]);
        printk (KERN_INFO "\n");
    }    
    else if (mrp_attribute_type == MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT)
    {
        MMRP_PRINT_INFO("%s %x\n", "Received Service Requirement: ", (u8)(*((u8*)attribute_value)));
    }
    else
    {
        MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "called with unknown mrp_attribute_type");
        return -EINVAL;
    }
    
    return 0;
}

static int MAC_attribute_value_compare(void * one, void * two) { return memcmp(one, two, MSRP_ATTRIBUTE_LENGTH_MAC); }
static int SR_attribute_value_compare(void * one, void * two)  { return memcmp(one, two, MSRP_ATTRIBUTE_LENGTH_SERVICE_REQUIREMENT); }

static mrp_attribute_value_compare mmrp_get_attribute_value_compare (int attribute_type)
{
    if (attribute_type == MMRP_ATTRIBUTE_TYPE_MAC)
        return MAC_attribute_value_compare;
    else if (attribute_type == MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT)
        return SR_attribute_value_compare;
    else
    {
        MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "called with unknown attribute_type");
        return NULL;
    }
}

static int mmrp_get_attribute_length (int attribute_type)
{
    if (attribute_type == MMRP_ATTRIBUTE_TYPE_MAC)
        return MSRP_ATTRIBUTE_LENGTH_MAC;
    else if (attribute_type == MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT)
        return MSRP_ATTRIBUTE_LENGTH_SERVICE_REQUIREMENT;
    else
    {
        MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "called with unknown attribute_type");
        return -EINVAL;
    }
}

static int mmrp_increment_first_value (int attribute_type, void * first_value, int how_much)
{
    u64 mac;
    u8 sr;

    mac = 0;
    sr = 0;
    
    if (attribute_type == MMRP_ATTRIBUTE_TYPE_MAC)
    {
        memcpy (((u8*)&mac) + 2, first_value, MSRP_ATTRIBUTE_LENGTH_MAC);
        mac = be64_to_cpu(mac);
        mac += how_much;
        mac = cpu_to_be64(mac);
        memcpy (first_value, ((u8*)&mac) + 2, MSRP_ATTRIBUTE_LENGTH_MAC);
    }
    else if (attribute_type == MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT)
    {
        memcpy (&sr, first_value, MSRP_ATTRIBUTE_LENGTH_SERVICE_REQUIREMENT);
        if (sr + how_much < MMRP_REQUIREMENT_SPECIFICATION_MIN || sr + how_much > MMRP_REQUIREMENT_SPECIFICATION_MAX)
            return -EINVAL;
        ((u8*)first_value)[0] += how_much;
    }
    else
    {
        MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "called with unknown attribute_type");
        return -EINVAL;
    }
    
    return 0;
}

static int mmrp_does_need_four_packed_events (int attribute_type) { return 0; }

static struct mrp_ops mmrp_mrp_ops = {
    .mrp_mad_join_indication            = mmrp_mad_join_indication,
    .mrp_mad_leave_indication           = mmrp_mad_leave_indication,
    .mrp_get_attribute_value_compare    = mmrp_get_attribute_value_compare,
    .mrp_get_attribute_length           = mmrp_get_attribute_length,
    .mrp_increment_first_value          = mmrp_increment_first_value,
    .mrp_does_need_four_packed_events   = mmrp_does_need_four_packed_events,
};
// ================================================================
// ================================================================

int mmrp_register_mac_address(u8 mac_address [6])
{
    if (mrp_is_address_an_mrp_application_address(mac_address) || mrp_is_address_a_vlan_component_reserved_address(mac_address))
        return -EINVAL;
    
    return mrp_mad_join_request_notify((struct mrp *)&mmrp, MMRP_ATTRIBUTE_TYPE_MAC, mac_address, 0, 0);
}
EXPORT_SYMBOL(mmrp_register_mac_address);

int mmrp_deregister_mac_address(u8 mac_address [6])
{
    if (mrp_is_address_an_mrp_application_address(mac_address) || mrp_is_address_a_vlan_component_reserved_address(mac_address))
        return -EINVAL;

    return mrp_mad_leave_request_notify ((struct mrp *)&mmrp, MMRP_ATTRIBUTE_TYPE_MAC, mac_address);
}
EXPORT_SYMBOL(mmrp_deregister_mac_address);

int mmrp_register_service_requirement(u8 requirement_specification)
{
    if (requirement_specification < MMRP_REQUIREMENT_SPECIFICATION_MIN || requirement_specification > MMRP_REQUIREMENT_SPECIFICATION_MAX)
        return -EINVAL;
        
    return mrp_mad_join_request_notify((struct mrp *)&mmrp, MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT, &requirement_specification, 0, 0);
}
EXPORT_SYMBOL(mmrp_register_service_requirement);

int mmrp_deregister_service_requirement(u8 requirement_specification)
{
    if (requirement_specification < MMRP_REQUIREMENT_SPECIFICATION_MIN || requirement_specification > MMRP_REQUIREMENT_SPECIFICATION_MAX)
        return -EINVAL;
        
    return mrp_mad_leave_request_notify ((struct mrp *)&mmrp, MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT, &requirement_specification);
}
EXPORT_SYMBOL(mmrp_deregister_service_requirement);

void mmrp_exit(void);

int mmrp_init (void)
{
    int ret;
    
    MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "start");
    
    ret = 0;
    
    if ((ret = mrp_init((struct mrp *)&mmrp, mrp_application_address_mmrp, MRP_ETHERTYPE_MMRP, port_name, &mmrp_mrp_ops, MMRP_PROTOCOL_VERSION, 0)))
        goto fail;
        
    MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "successful");
    return 0;
    
fail:
    MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "failed");
    mmrp_exit ();
    return ret;
}

void mmrp_exit(void)
{
    MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "start");
    
    mrp_clean_up ((struct mrp *)&mmrp);
    
    MMRP_PRINT_INFO("%s %s\n", __FUNCTION__, "successful");
}

module_init (mmrp_init);
module_exit (mmrp_exit);
